Using ClipNode for Masking Effects
In game development and graphics rendering, masking is a common technique used to control the visible area of an image or scene. In Dora SSR, we can easily implement various masking effects using the ClipNode
class. This tutorial will guide you through using ClipNode
to achieve shape-based and image-based masking effects with sample code.
1. What is ClipNode?
ClipNode
is a scene node class in Dora SSR that allows you to set up a clipping mask to control the visible area of its child nodes. By specifying a mask node (stencil
), ClipNode
clips the rendering content of its child nodes based on the mask's shape or transparency.
1.1 Main Properties of ClipNode
- stencil: Defines the mask node that determines the clipping shape.
- alphaThreshold: The alpha threshold (ranging from 0 to 1) that determines which mask pixels participate in clipping. Clipping only applies when the mask pixel's alpha value exceeds this threshold.
- inverted: Inverts the clipping area. When set to
true
, the clipped and unclipped areas are swapped.
2. Example A: Using Any Shape as a Mask
In this first example, we will use a star-shaped mask to clip the visible area of an animated model.
2.1 Step-by-step Explanation
- Create the Vertex Data for a Star Shape
- Lua
- Teal
- TypeScript
- YueScript
local function StarVertices(radius, line)
-- Calculate the vertex coordinates of a star
local a = math.rad(36)
local c = math.rad(72)
local f = math.sin(a) * math.tan(c) + math.cos(a)
local R = radius
local r = R / f
local vecs = {}
local count = 1
for i = 9, line and -1 or 0, -1 do
local angle = i * a
local cr = i % 2 == 1 and r or R
vecs[count] = Vec2(cr * math.sin(angle), cr * math.cos(angle))
count = count + 1
end
-- Return the vertex array
return vecs
end
local function StarVertices(radius: number, line: integer): {Vec2.Type}
-- Calculate the vertex coordinates of a star
local a = math.rad(36)
local c = math.rad(72)
local f = math.sin(a) * math.tan(c) + math.cos(a)
local R = radius
local r = R / f
local vecs: {Vec2.Type} = {}
local count = 1
for i = 9, line and -1 or 0, -1 do
local angle = i * a
local cr = i % 2 == 1 and r or R
vecs[count] = Vec2(cr * math.sin(angle), cr * math.cos(angle))
count = count + 1
end
-- Return the vertex array
return vecs
end
function StarVertices(radius: number, line: number) {
// Calculate the vertex coordinates of a star
const a = math.rad(36);
const c = math.rad(72);
const f = math.sin(a) * math.tan(c) + math.cos(a);
const R = radius;
const r = R / f;
const vecs: Vec2.Type[] = []
for (let i = 9; line ? -1 : 0; i--) {
const angle = i * a;
const cr = i % 2 == 1 ? r : R
vecs.push(Vec2(cr * math.sin(angle), cr * math.cos(angle)));
}
// Return the vertex array
return vecs;
}
StarVertices = (radius, line) ->
-- Calculate the vertex coordinates of a star
a = math.rad 36
c = math.rad 72
f = math.sin(a) * math.tan(c) + math.cos a
R = radius
r = R / f
vecs = []
for i = 9, line and -1 or 0, -1
angle = i * a
cr = i % 2 == 1 and r or R
vecs[] = Vec2 cr * math.sin(angle), cr * math.cos angle
-- Return the vertex array
vecs
- Draw the Mask Shape
- Lua
- Teal
- TypeScript
- YueScript
local maskA = DrawNode()
maskA:drawPolygon(StarVertices(160, false))
local maskA = DrawNode()
maskA:drawPolygon(StarVertices(160, false))
const maskA = DrawNode();
maskA.drawPolygon(StarVertices(160, false));
maskA = DrawNode()
maskA.drawPolygon StarVertices(160, false)
Here, we use DrawNode
to draw a star shape as the mask node.
- Create an Animated Model
- Lua
- Teal
- TypeScript
- YueScript
local targetA = Model("Model/xiaoli.model")
targetA.look = "happy"
targetA.fliped = true
-- Set model's animation and movement path
targetA:play("walk", true)
targetA:runAction(
Sequence(
X(1.5, -200, 200), Event("Turn"),
X(1.5, 200, -200), Event("Turn")
), true
)
targetA:slot("Turn", function()
targetA.fliped = not targetA.fliped
end)
local targetA = Model("Model/xiaoli.model")
if not targetA is nil then
targetA.look = "happy"
targetA.fliped = true
-- Set model's animation and movement path
targetA:play("walk", true)
targetA:runAction(
Sequence(
X(1.5, -200, 200), Event("Turn"),
X(1.5, 200, -200), Event("Turn")
), true
)
targetA:slot("Turn", function()
targetA.fliped = not targetA.fliped
end)
end
const targetA = Model("Model/xiaoli.model");
if (targetA) {
targetA.look = "happy";
targetA.fliped = true;
// Set model's animation and movement path
targetA.play("walk", true);
targetA.runAction(
Sequence(
X(1.5, -200, 200), Event("Turn"),
X(1.5, 200, -200), Event("Turn")
), true
);
targetA.slot("Turn", () => {
targetA.fliped = !targetA.fliped;
});
}
targetA = with Model "Model/xiaoli.model"
.look = "happy"
.fliped = true
-- Set model's animation and movement path
.play "walk", true
\runAction Sequence(
X 1.5, -200, 200, Event "Turn",
X 1.5, 200, -200, Event "Turn"
), true
\slot "Turn", ->
targetA.fliped = not targetA.fliped
- Create a ClipNode and Add Child Nodes
- Lua
- Teal
- TypeScript
- YueScript
local clipNodeA = ClipNode(maskA)
clipNodeA:addChild(targetA)
clipNodeA.inverted = true
local clipNodeA = ClipNode(maskA)
clipNodeA:addChild(targetA)
clipNodeA.inverted = true
const clipNodeA = ClipNode(maskA);
clipNodeA.addChild(targetA);
clipNodeA.inverted = true;
clipNodeA = with ClipNode maskA
\addChild targetA
.inverted = true
We pass the mask node maskA
to ClipNode
, and add the animated model targetA
as its child node.
- Add to the Scene
- Lua
- Teal
- TypeScript
- YueScript
local exampleA = Node()
exampleA:addChild(clipNodeA)
local exampleA = Node()
exampleA:addChild(clipNodeA)
const exampleA = Node();
exampleA.addChild(clipNodeA);
exampleA = with Node!
\addChild clipNodeA
Result
After running the above code, you will see the model visible only within the star shape, with the parts outside the star clipped away.
3. Example B: Using an Image with alphaThreshold for Masking
In the second example, we will use a model with transparency as the mask and utilize the alphaThreshold
property to control the clipping effect.
3.1 Step-by-step Explanation
- Create the Mask Model
- Lua
- Teal
- TypeScript
- YueScript
local maskB = Model("Model/xiaoli.model")
maskB.look = "happy"
maskB.fliped = true
maskB:play("walk", true)
local maskB = Model("Model/xiaoli.model")
if not maskB is nil then
maskB.look = "happy"
maskB.fliped = true
maskB:play("walk", true)
end
const maskB = Model("Model/xiaoli.model");
if (maskB) {
maskB.look = "happy";
maskB.fliped = true;
maskB.play("walk", true);
}
maskB = with Model "Model/xiaoli.model"
.look = "happy"
.fliped = true
.play "walk", true
Here, we use a model as the mask node.
- Create the Target Shape
- Lua
- Teal
- TypeScript
- YueScript
local targetB = DrawNode()
targetB:drawPolygon(StarVertices(160, false))
-- Set the movement path for the target shape
targetB:runAction(
Sequence(
X(1.5, -200, 200),
X(1.5, 200, -200)
), true
)
local targetB = DrawNode()
targetB:drawPolygon(StarVertices(160, false))
-- Set the movement path for the target shape
targetB:runAction(
Sequence(
X(1.5, -200, 200),
X(1.5, 200, -200)
), true
)
const targetB = DrawNode();
targetB.drawPolygon(StarVertices(160, false));
// Set the movement path for the target shape
targetB.runAction(
Sequence(
X(1.5, -200, 200),
X(1.5, 200, -200)
), true
);
targetB = with DrawNode!
\drawPolygon StarVertices 160, false
-- Set the movement path for the target shape
\runAction Sequence(
X 1.5, -200, 200,
X 1.5, 200, -200
), true
- Create a ClipNode and Set alphaThreshold
- Lua
- Teal
- TypeScript
- YueScript
local clipNodeB = ClipNode(maskB)
clipNodeB:addChild(targetB)
clipNodeB.inverted = true
clipNodeB.alphaThreshold = 0.3
local clipNodeB = ClipNode(maskB)
clipNodeB:addChild(targetB)
clipNodeB.inverted = true
clipNodeB.alphaThreshold = 0.3
const clipNodeB = ClipNode(maskB);
clipNodeB.addChild(targetB);
clipNodeB.inverted = true;
clipNodeB.alphaThreshold = 0.3;
clipNodeB = with ClipNode maskB
\addChild targetB
.inverted = true
.alphaThreshold = 0.3
By setting alphaThreshold = 0.3
, clipping will only apply when the mask model's pixel alpha value is greater than 0.3.
- Add to the Scene
- Lua
- Teal
- TypeScript
- YueScript
local exampleB = Node()
exampleB:addChild(clipNodeB)
local exampleB = Node()
exampleB:addChild(clipNodeB)
const exampleB = Node();
exampleB.addChild(clipNodeB);
exampleB = with Node!
\addChild clipNodeB
3.2 Result
After running the code, you will notice that the target shape is visible only in the non-transparent parts of the mask model, creating a transparency-based masking effect.
4. Conclusion
Through these two examples, we've learned how to use ClipNode
in Dora SSR to implement different masking effects:
- Example A showed how to use any drawn shape as a mask to clip the display area of child nodes.
- Example B demonstrated how to use the
alphaThreshold
property to clip using an image or model with transparency.
4.1 Tips
- Using alphaThreshold: When the mask node contains semi-transparent areas,
alphaThreshold
is useful. It allows you to finely control which pixels participate in clipping. - Inverted Property: By toggling
inverted
, you can easily reverse the clipping area to achieve different visual effects.
We hope this tutorial helps you better understand and apply ClipNode
in Dora SSR to create diverse masking effects. For any questions, feel free to consult the official documentation or join community discussions.